import os
import re
from mediadart import log
from uuid import uuid4

class StorageError(Exception): pass

def new_id(filename=''):
    'returns a unique hex id, with the extension of filename attached if there is any'
    res_id = uuid4().get_hex()
    fileparts = filename.split('.')
    if len(fileparts) > 1:
        ext = fileparts[-1]
        if ext:
            res_id = '%s.%s' % (res_id, ext)
    return res_id

        

class Storage:
    __depth = 2   # the number of subdirectories in the trees: max 32
    def __init__(self, basedir):
        os.stat(basedir)
        assert(self.__depth<=16)
        self._basedir = basedir
        self.r = re.compile('[\da-f]{32}')

    def _create(self, curdir, res_id, filename):
        "create res_id, raise if exists"
        log.debug('copy %s in %s' % (filename, res_id))
        path = os.path.join(curdir, res_id)
        if os.path.exists(path):
            raise StorageError('file exists')
        tmppath = os.path.join(curdir, '%s,tmp' % res_id)
        shutil.move(filename, tmppath)
        open(tmppath, 'w').write(open(filename, 'r').read())
        os.rename(tmppath, path)
        return 0

    def _delete(self, curdir, res_id):
        "delete res_id, silent if exists"
        log.debug('deleting %s/%s' % (curdir, filename))
        path = os.path.join(curdir, filename)
        if os.path.exists(path):
            os.unlink(os.path.join(curdir, filename))
        return 0

    def _walk(self, level, res_id, curdir, callback, callback_args):
        if level >= self.__depth:
            return callback(curdir, *callback_args)
        else:
            curdir = os.path.join(curdir, res_id[level*2: (level+1)*2])
            if not os.path.exists(curdir):
                os.mkdir(curdir)
            return self._walk(level+1, res_id, curdir, callback, callback_args)

    # External interface
    def add(self, filename, res_id = None, move=False):
        if res_id:
            if not self.is_valid(res_id):
                raise StorageError('invalid id: %s' % res_id)
        else:
            res_id = new_id(filename)
        try:
            self._walk(0, res_id, self._basedir, self._create, [res_id, filename])
        except StorageError, e:
            log.debug('Error: %s' % str(e))
        return res_id

    def delete(self, res_id):
        """delete the file from the cache if it exists. Otherwise do nothing."""
        path = self.abspath(res_id)
        if os.path.isfile(path):
            os.unlink(path)
        return 0

    def exists(self, res_id):
        return os.path.exists(self.abspath(res_id))

    def relpath(self, res_id):
        if not self.is_valid(res_id):
            raise StorageError('invalid id: %s' % res_id)
        return os.path.join(*([res_id[x:x+2] for x in xrange(0, self.__depth*2, 2)]+[res_id])) 

    def abspath(self, res_id):
        return os.path.join(self._basedir, self.relpath(res_id))

    def is_valid(self, res_id):
        "returns True if res_id is a valid id"
        return (self.r.match(res_id) is not None)

if __name__=='__main__':
    if not os.path.isdir('/tmp/prova'):
        os.mkdir('/tmp/prova')
    fc=Storage('/tmp/prova')
    i = fc.add('__init__.py')
    print 'added', i
    print 'exists?: ', fc.exists(i)
    fc.delete(i)
    print 'deleted', i

